#ifndef __tenacitas_log_core__logger__h__
#define __tenacitas_log_core__logger__h__


/** @file
    In this file is defined the @p tenacitas::log::core::logger class
*/

/** @todo */


// ==> C++ Headers
#include <string>
#include <cstdint>
#include <utility>
#include <memory>
#include <thread>
#include <functional>
#include <map>
#include <mutex>

// ==> 3rds Headers

// ==> Our Headers

#include <tenacitas.return_code.core/rc.h>

#include "level.h"
#include "thread_id.h"
#include "src/internal_logger.h"
#include "src/internal_threads.h"

// ==> Namespaces
using namespace std;

// ==> Macro Constants
/** */

// ==> Macro Commands

/** This macro allows a message to be logged in @p tenacitas::log::level::trace
    level 

    @param  logger is an instance of some specialization of @p
    tenacitas::log::logger class */
#define log_trace(logger) logger.new_line(tenacitas::log::core::level::trace, __FILE__, __LINE__)

/** This macro allows a message to be logged in @p
    tenacitas::log::core::level::debug level  

    @param  logger is an instance of some specialization of @p
    tenacitas::log::logger class */
#define log_debug(logger) logger.new_line(tenacitas::log::core::level::debug, __FILE__, __LINE__)

/** This macro allows a message to be logged in @p
    tenacitas::log::core::level::info level  

    @param  logger is an instance of some specialization of @p
    tenacitas::log::logger class */
#define log_info(logger) logger.new_line(tenacitas::log::core::level::info, __FILE__, __LINE__)

/** This macro allows a message to be logged in @p
    tenacitas::log::core::level::warn level  

    @param  logger is an instance of some specialization of @p
    tenacitas::log::logger class */
#define log_warn(logger) logger.new_line(tenacitas::log::core::level::warn, __FILE__, __LINE__)

/** This macro allows a message to be logged in @p
    tenacitas::log::core::level::error level  

    @param  logger is an instance of some specialization of @p
    tenacitas::log::logger class */
#define log_error(logger) logger.new_line(tenacitas::log::core::level::error, __FILE__, __LINE__)

/** This macro allows a message to be logged in @p
    tenacitas::log::core::level::fatal level, and causes the program to exit 

    @param  logger is an instance of some specialization of @p
    tenacitas::log::logger class

    @param msg is the message to be logged before the program to exit
*/  
#define log_fatal(logger, msg) logger.new_line(tenacitas::log::core::level::fatal, __FILE__, __LINE__) << msg; \
    exit(1);                                                            \


/** ==> namespace of the organization */
namespace tenacitas {
    /** ==> namespace of the project */
    namespace log {
        /** ==> perspective */
        namespace core {

            // ==> Typedefs

            // ==> Pre-Declarations

            // ==> Attributes

            // ==> Functions
        
            // ==> Classes

            /** Base class that provides logging facilities

                The main goal of this class is to log messages without the need
                of some type of concurrent access protection, like mutual
                exclusion objects. This is accomplished by separeting log
                messages by thread. Within the same thread, log messsages can be
                logged, no matter the media where they are being logged, without
                concurrence.

                For instance, if the media where the log messages will be saved
                is a file, a log file can be created for each thread. So, all the
                messages for a thread will be sequentially logged in the same
                file, without concurrence. 

                @tparam supplier provides the types needed for @p logger to
                work, listed below.

                @tparam supplier::media is the media where the messages will be
                logged. It must implement:

                @code
                media(const thread_id & p_thread_id), which creates a @p media
                object for the current thread id 

                media(media &&), the move constructor

                media & operator=(media &&), the move assingment

                rc<> new_line(level p_level,
                              uint64_t p_timestamp,
                              const char * p_file,
                              int64_t p_line) noexcept. which should start a new
                line, or log record. When a new line starts, it is informed the
                the log level, a timestamp, the name of the file, the function,
                and line number where the new log line is being generated. 

                template<typename data>
                rc<> write (const data & p_data) noexcept, which writes @p p_data

                template<typename data>
                rc<> write (data && p_data) noexcept, which writes @p p_data

                medid & operator = (media &&), the move assingment

                @endcode

                <b>A word to the programmer user</b>
                You should avoid using the
                tenacitas::log::core::logger::new_line and <tt> operator<<
                </tt> members, and use the macros commands @p log_trace, @p
                log_debug, @p log_info, @p log_warn, @p log_error or @p log_fatal
                instead. These macros are defined in the beginning of this header
                file.  

                You should use the static method
                tenacitas::log::logger::set_all to define the
                tenacitas::log::level to be used by all the @p logger objects.

                You can change the tenacitas::log::level for all the @p logger
                objects by calling tenacitas::log::logger::set_level_all.

                When creating a @p logger, you can define a tenacitas::log::level
                to be used only for it, when calling its constructor.

                @nosubgrouping
            */
            template <typename supplier>
            class logger {
            public:

                friend class logger_tester;

                // ==> logger - Public Internal Types

                /** Type for the media where the log messages will be actually
                    written */
                typedef typename supplier::media media;


                /** Translatable constants */
                enum constants : constant_id {could_not_create_logger = 100};
                

                // ==> logger - Public Constructors

                /** Creates a logger object, specifying a log level for it
                 @param p_level is the log level for this logger object 
                 @throw rc<> */
                logger(const level & p_level = level::info);

                /** Destructor */
                ~logger();

                /** */
                logger(logger &) = delete;

                /** not allowed */
                logger(logger &&) = delete;
            
                // ==> logger - Public Accessors

                /** Resets the log level defined to this @p logger object

                    @param  p_level is the new tenacitas::log::level to be used
                    by this @p logger object */
                void set_level(const level & p_level) noexcept;

                /** @return the log level for this @p logger object */
                const level & get_level() const noexcept;

                /** @return the id of the thread where the log was created */
                thread_id get_thread_id() const noexcept;

                /** Resets the log level for all @p logger objects

                    @param  p_level is the new tenacitas::log::level to be used
                    by all @p logger objects*/
                static void set_level_all(const level & p_level) noexcept;

                /** @return the log level for all @p logger objects */
                static const level & get_level_all() noexcept;

                // ==> logger - Public Processors

                /** Starts a new log line

                    @attention: One should not call this method directly, but use
                    the @p log_trace, @p log_debug, @p log_info, @p log_warn,
                    @p logic_error or @p log_fatal

                    This method informs the @p media class that a new line is
                    starting. Depending on the concrete type of the media, this
                    can mean a new line in a file, a new record in a database
                    table, or anything else.

                    When a new log line is about to be created, if @p logger
                    realizes that the thread is not the same anymore, it creates
                    a new internal logger object for this new thread.

                    @param  p_level is the tenacitas::log::level of the new line
                    @param  p_file is the source file where the log is occurring
                    @param  p_line is the line it the @p p_file where the log is
                    occurring

                    @return the same @p logger object, if the thread is still the
                    same; or a new @p loggger object, if the thread changed

                    @throw 
                */
                logger & new_line(const level & p_level,
                                  const char * p_file,
                                  int64_t p_line);

                // ==> logger - Public Operators


                /** Outputs any kind of data

                    @tparam t_data is the type of the data to be logged

                    @param p_data is the data to be logged
                */
                template <typename t_data>
                logger & operator << (const t_data & p_data);

                /** Outputs any kind of data

                    @tparam t_data is the type of the data to be logged

                    @param p_data is the data to be logged
                */
                template <typename t_data>
                logger & operator << (t_data && p_data);

                /** Logs a tenacitas::return_code::core::rc object */
                template <typename obj = void *>
                logger & operator << (const rc<obj> &);

                /** not allowed */
                void* operator new (std::size_t size)
                    throw (std::bad_alloc) = delete;

                /** not allowed */
                void operator delete (void *p_logger) = delete;

                /** not allowed */
                void *operator new[] (size_t) = delete;

                /** not allowed */
                void operator delete[] (void *) = delete;

                /** not allowed */
                logger & operator=(const logger &)=delete;

                /** not allowed */
                logger & operator=(logger &&)=delete;

                // ==> logger - Public Attributes
                static const class_id id = 1432749440161ULL;
                
            private:

                // ==> logger - Private Types

                /** */
                typedef core::internal_threads<supplier> internal_threads;

                /** */
                typedef core::internal_logger<supplier> internal_logger;
                

                // ==> logger - Private Attributes

                /** */
                static internal_threads m_threads;

                /** Current current in use */
                internal_logger * m_current;

            };
        }
    }
}

// =============================================================================
//                     I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::log::core;


// #############################################################################
// logger

// -----------------------------------------------------------------------------
template <typename supplier>
typename logger<supplier>::internal_threads
logger<supplier>::m_threads;

// -----------------------------------------------------------------------------
template <typename supplier>
const class_id
logger<supplier>::id;

// -----------------------------------------------------------------------------
template <typename supplier>
logger<supplier>::
logger(const level & p_level)
    : m_current(nullptr) {

    rc<internal_logger *> _rc = m_threads.set_current(p_level);
    if (!_rc)  {
        rc<> _rc1(_rc);
        std::cerr << _rc1 << std::endl;
        throw _rc1;
    }
    
    m_current = _rc.get_obj();
}


// -----------------------------------------------------------------------------
template <typename supplier>
inline logger<supplier>::
~logger() { }


// -----------------------------------------------------------------------------
template <typename supplier>
inline void
logger<supplier>::
set_level(const level & p_level) noexcept {
    m_current->set_level(p_level);
}

// -----------------------------------------------------------------------------
template <typename supplier>
inline const level &
logger<supplier>::
get_level() const noexcept {
    return m_current->get_level();
}

// -----------------------------------------------------------------------------
template <typename supplier>
thread_id
logger<supplier>::
get_thread_id() const noexcept {return m_current->get_thread_id();}


// -----------------------------------------------------------------------------
template <typename supplier>
inline void
logger<supplier>::
set_level_all(const level & p_level) noexcept {
    internal_logger::set_level_all(p_level);
}

// -----------------------------------------------------------------------------
template <typename supplier>
inline const level &
logger<supplier>::
get_level_all() noexcept {
    return internal_logger::get_level_all();
}
        
// -----------------------------------------------------------------------------
template <typename supplier>
logger<supplier> &
logger<supplier>::
new_line(const level & p_level,
         const char * p_file,
         int64_t p_line) {

    
    // check if it is still in the same thread     
    if (m_current->get_thread_id() != thread_id()) {
        // NO! So we check if a current for this thread was alredy
        // created
        rc<internal_logger *> _rc =
            m_threads.set_current(internal_logger::get_level_all());
        if (!_rc) {
            rc<> _rc1(_rc);
            std::cerr << _rc1 << std::endl;
            throw _rc1;
        }
        m_current = _rc.get_obj();
    }

    rc<> _rc2 (m_current->new_line(p_level, p_file, p_line));
    if (!_rc2) {
        std::cerr << _rc2 << std::endl;
        throw _rc2;
    }

    return *this;

}

// -----------------------------------------------------------------------------
template <typename supplier>
template <typename t_data>
inline logger<supplier> &
logger<supplier>::
operator << (const t_data & p_data) {
    (*m_current) << p_data;
    return *this;
}

// -----------------------------------------------------------------------------
template <typename supplier>
template <typename t_data>
inline logger<supplier> &
logger<supplier>::
operator << (t_data && p_data) {
    (*m_current) << p_data;
    return *this;
}


#endif
